/******************************************************************************
 * SAGE - Scalable Adaptive Graphics Environment
 *
 * Module: sageMemory.cpp - memory management class in SAGE
 * Author : Byungil Jeong
 *
 * Copyright (C) 2004 Electronic Visualization Laboratory,
 * University of Illinois at Chicago
 *
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the distribution.
 *  * Neither the name of the University of Illinois at Chicago nor
 *    the names of its contributors may be used to endorse or promote
 *    products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Direct questions, comments etc about SAGE to bijeong@evl.uic.edu or 
 * http://www.evl.uic.edu/cavern/forum/
 *
 *****************************************************************************/

#include "sageMemory.h"

sageMemSegment* sageMemSegment::partition(int firstSize)
{
   if (firstSize >= size) {
      sage::printLog("sageMemSegment::partition : partition size is bigger than segment size");
      return NULL;
   }
   
   sageMemSegment *second = (sageMemSegment *)(data + firstSize);
   second->size = size - firstSize - sizeof(sageMemSegment);
   second->next = next;
   second->free = true;
   next = second;
   size = firstSize;
   
   return second;
}

bool sageMemSegment::merge(int reqSize)
{
   while(size < reqSize) {
      if (next) {
         if (next->free) {
            size += next->size + sizeof(sageMemSegment);
            next = next->next;
         }
         else
            return false;
      }
      else
         return false;
   }
   
   return true;
}

sageMemory::sageMemory(int size)
{
   if (size == 0) {
      buffer = NULL;
      bufSize = 0;
   }
   else {
      buffer = new char[size];
      bufSize = size;
   }   
   
   headerSize = sizeof(sageMemSegment);
   head = (sageMemSegment *)buffer;
   head->size = size - headerSize;
   head->next = NULL;
   head->free = true;
   curPt = head;
   
   memMutex = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
   pthread_mutex_init(memMutex, NULL);
   pthread_mutex_unlock(memMutex);
   notFull = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
   pthread_cond_init(notFull, NULL);
}

sageMemory::~sageMemory()
{
   if (buffer)
      delete [] buffer;
      
   if (memMutex)
      free(memMutex);
}

sageMemSegment* sageMemory::checkCurSegment(int size)
{
   sageMemSegment *newSegment = NULL;
   
   if (curPt->size == size) {
      curPt->free = false;
      newSegment = curPt;
      findFreeSegment();
   }
   else if (curPt->size > size) {
      newSegment = curPt;
      curPt = curPt->partition(size);
      newSegment->free = false;
   }
   
   return newSegment;
};

bool sageMemory::findFreeSegment()
{
   sageMemSegment *startPt = curPt;
   curPt = curPt->next;
   
   
   while (startPt != curPt) {
      if (curPt) {
         if (curPt->free) {
            return true;
         }   
         else
            curPt = curPt->next;   
      }
      else
         curPt = head;
   }
   
   return false;
}

char* sageMemory::allocate(int size)
{
   sageMemSegment *newSegment = NULL;
   
   pthread_mutex_lock(memMutex);
   
   //std::cout << "try to find a free segment" << std::endl;
   while(!newSegment) {
      // check if all memory space are used
      if (!curPt->free)
         while (!findFreeSegment()) {
         //sage::printLog("sageMemory::allocate : waiting for a free segment");
         if (sage::condition_wait(notFull, memMutex, 1) == SAGE_TIMEOUT) {
            sage::printLog("sageMemory::allocate : waiting time-out. give up memory allocation");
            return NULL;
         }
      }   

      // current segment should be free.here
      // check if the current segment fits to the requested memory size      
      newSegment = checkCurSegment(size);

      if (!newSegment){
         // try to merge with the next segment and see if the request fits to the merged segment
         if (curPt->merge(size)) {
            newSegment = checkCurSegment(size);
            if (!newSegment) {
               sage::printLog("sageMemory::allocate : error in merging segments");
            }
         }
         else {
            sageMemSegment *startPt = curPt;
            findFreeSegment();

            while(curPt != startPt) {
               newSegment = checkCurSegment(size);
               if (newSegment) {
                  break;
               }
               else {
                  if (curPt->merge(size)) {
                     newSegment = checkCurSegment(size);
                     if (!newSegment)
                        sage::printLog("sageMemory::allocate : error in merging segments");
                     else
                        break;
                  }
                  else   
                     if (!findFreeSegment())
                     break;
               }
            } // end while   
         }
      }
      
      if (!newSegment) {
         //sage::printLog("sageMemory::allocate : waiting for an enough size of segment");
         if (sage::condition_wait(notFull, memMutex, 1) == SAGE_TIMEOUT) {
            sage::printLog("sageMemory::allocate : waiting time-out. give up memory allocation");
            return NULL;
         }
      }
   }

   //std::cout << "get free segment" << std::endl;   
   pthread_mutex_unlock(memMutex);

   return newSegment->data;
}

int sageMemory::deallocate(char *ptr)
{
   sageMemSegment *seg = (sageMemSegment *)(ptr - headerSize);
   
   pthread_mutex_lock(memMutex);
   seg->free = true;
   pthread_mutex_unlock(memMutex);
   
   pthread_cond_signal(notFull);
   
   return 0;
}
